package isnork.g2;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;

import isnork.g2.Observation_Ext;
import isnork.g2.utility.Utility;
import isnork.g2.iSnorkConfig;
import isnork.sim.GameConfig;
import isnork.sim.Observation;
import isnork.sim.Player;
import isnork.sim.SeaLifePrototype;
import isnork.sim.iSnorkMessage;
import isnork.sim.GameObject.Direction;

public class G2Explorer extends Player {

	iSnorkConfig config;
	private static final Point2D BOAT = new Point2D.Double(0,0);
	Set<SeaLifePrototype> seaLifePossibilites;
	Map<String, SeaLifePrototype> seaLifeDict;
	int penalty;
	int d;
	int r;
	int n;
	int remCount;
	double maxHappiness;
	double maxDanger;
	Direction lastDirection; 
	Point2D location;
	Point2D lastLocation;
	Set<Observation> whatYouSee;
	Set<Observation_Ext> obs_ext;
	Map<Integer, Observation_Ext> obs_prev;
	Set<Observation> whatYouHaveToSee;
	Set<iSnorkMessage> incomingMessages;
	Set<Observation> playerLocations;
	List<Strategy> strategies;
	int playRounds = 480;
	Point2D prevLocation;
	int current_round = 0;
	Double allHapp = 0.0;
	Double expHapp = 0.0;
	Double allDanger = 0.0;
	Double movDanger = 0.0;
	Double allSeaCreatures = 0.0;
	Double dangerCreatures = 0.0;
	Double movDangerCreatures = 0.0;
	Double expHappDensity = 0.0;
	double safeDegree;
	double dangerPerCell;
	int tick = 0;
	double suffered = 0;
	Set<Point2D> Visited = new HashSet<Point2D>();
	Queue<Point2D> toView = new LinkedList<Point2D>();
	Point2D first;
	double maxMovDanger = 0;
	Map<SeaLifePrototype, Integer> number_times_Species = new HashMap<SeaLifePrototype, Integer>();
	Map<SeaLifePrototype, Set<Integer> > number_times_Creature = new HashMap<SeaLifePrototype, Set<Integer> >();
	public int justBack = 0;
	@Override
	public String getName() {
		return "g2Explorer";
	}

	@Override
	public void newGame(Set<SeaLifePrototype> seaLifePossibilites, int penalty,
			int d, int r, int n) {
		// System.out.printf("new game called\n");
		// make a record for the game configuration
		this.seaLifePossibilites = seaLifePossibilites;
		this.config = new iSnorkConfig(this);
		this.penalty = penalty;
		this.d = d; // dimension
		this.r = r; // diver radius
		this.n = n; // number of divers
		remCount = 1; // to remember multiple turns
		seaLifeDict = new HashMap<String, SeaLifePrototype>();
		lastDirection = Direction.STAYPUT;
		lastLocation = BOAT;
		for (SeaLifePrototype s : seaLifePossibilites) {
			number_times_Species.put(s, 0);
			Set<Integer> emptyMap = new HashSet<Integer>();
			number_times_Creature.put(s, emptyMap);
			seaLifeDict.put(s.getName(), s);
			allHapp += s.getHappiness() * (s.getMaxCount() + s.getMinCount())
					/ 2;
			// should we change expHapp to s.getHappiness()*1.75?
			if (s.getMinCount() == 0) {
				if (s.getMaxCount() > 5)
					expHapp += s.getHappiness() * 1;
			} else if (s.getMinCount() == 1) {
				expHapp += s.getHappiness() * 1;
			} else if (s.getMinCount() == 2) {
				expHapp += s.getHappiness() * 1.5;
			} else {
				expHapp += s.getHappiness() * 1.75;
			}

			allSeaCreatures += (s.getMaxCount() + s.getMinCount()) / 2;
			expHappDensity = (allHapp / allSeaCreatures);
			if (s.getHappinessD() > maxHappiness) {
				maxHappiness = s.getHappinessD();
			}
			if (s.isDangerous()) {
				allDanger += s.getHappiness()
						* (s.getMaxCount() + s.getMinCount()) / 2;
				dangerCreatures += (s.getMaxCount() + s.getMinCount()) / 2;
				if (s.getSpeed() != 0) {
					movDanger += s.getHappiness()
							* (s.getMaxCount() + s.getMinCount()) / 2;
					if (maxMovDanger < s.getHappiness()){
						maxMovDanger = s.getHappinessD();
					}
					++movDangerCreatures;
				}

				if (this.maxDanger < s.getHappiness()) {
					maxDanger = s.getHappinessD();
				}
			}

		}
		obs_ext = new HashSet<Observation_Ext>();
		obs_prev = new HashMap<Integer, Observation_Ext>();

		strategies = new ArrayList<Strategy>();

		if (allDanger < expHapp / 4) {
			safeDegree = 1;
		} else if (movDanger > (allHapp * 0.5)
				&& dangerCreatures * 25 / (4*4*d) > 3) {
			safeDegree = 0.6;
		} else if (movDanger > (allHapp * 0.4)
				&& dangerCreatures * 25 / (4*d*d) > 2) {
			safeDegree = 0.7;
		} else if (allDanger > (allHapp * 0.4)
				&& dangerCreatures * 25 / (4*d*d) > 1.5) {
			safeDegree = 0.8;
		} else {
			safeDegree = 0.9;
		}
		dangerPerCell = allDanger / (4*d*d);
		
		//System.out.println(safeDegree);
		// All the strategies we are using:
		//strategies.add(new BackToBoat(this));
		//strategies.add(new Rout(this));
		if(movDanger>=allHapp){
			safeDegree=1;
			if(this.r<=4){
				if(this.d<=15){
					//safeDegree = 0.6;
				}
				
				strategies.add(new PiranhaDangerAvoidance());
			}
			//strategies.add(new ObservationBasedDangerAvoidance());
		}
		else{
			strategies.add(new BackToBoat(this));
			strategies.add(new Route(this));
			strategies.add(new iSnorkStrategy());
		}
		strategies.add(new InferenceBasedDangerAvoidance());

		strategies.add(new Satisfied(this));
		strategies.add(new ReturnToBoatDirectly(this));
//		strategies.add(new Explore(this));
		

	}

	@Override
	public String tick(Point2D myPosition, Set<Observation> whatYouSee,
			Set<iSnorkMessage> incomingMessages,
			Set<Observation> playerLocations) {
		current_round++;
		for (Observation obs: whatYouSee){
			SeaLifePrototype prototype = this.seaLifeDict.get(obs.getName());
			Integer i = number_times_Species.get(prototype);
			Set<Integer> ids = number_times_Creature.get(prototype);
			if(ids == null)
			{
				ids = new HashSet<Integer>();
				i = new Integer(1);
			}
			else
			{
				if (!ids.contains(obs.getId()))
					i += 1;
				ids.add(obs.getId());
				number_times_Creature.put(prototype, ids);
			}
			number_times_Species.put(prototype, i);
		}
		// memorizing what happened
		this.location = myPosition;
		this.whatYouSee = whatYouSee;
		this.incomingMessages = incomingMessages;
		this.playerLocations = playerLocations;
		//this.all_you_saw.addAll(this.whatYouSee);
		
			if (location != null)
				Visited.add(location);
		// obs_ext is Observation with prototype and time
		obs_ext.clear();
		for (Observation obs : whatYouSee) {
			SeaLifePrototype prototype = seaLifeDict.get(obs.getName());
			if (prototype == null)
				continue;
			Observation_Ext saw = obs_prev.get(obs.getId());
			if (saw != null && saw.location.equals(obs.getLocation()))
				obs_ext.add(new Observation_Ext(obs.getId(), obs.getLocation(),
						obs.getDirection(), saw.time + 1, prototype, prototype
								.getHappinessD()));
			else
				obs_ext.add(new Observation_Ext(obs.getId(), obs.getLocation(),
						obs.getDirection(), 1, prototype, prototype
								.getHappinessD()));

			if (obs.isDangerous()
					&& obs.getLocation().distance(myPosition) < 1.5 && !this.location.equals(BOAT)) {
				suffered += obs.happiness();
			}

		}

		// obs_prev stores the obs_ext last time
		obs_prev.clear();
		for (Observation_Ext obs : obs_ext) {
			obs_prev.put(obs.id, obs);
		}
		if(this.current_round%20==0){
			this.lastLocation = this.location; 
		}
		

		
		return config.getMsgToTransmit(this);
	}

	@Override
	public Direction getMove() {
		// System.out.printf("get move called\n");
		// initialize utility accumulators for all directions
		Map<Direction, Double> utility_accumulators = new HashMap<Direction, Double>();
		for (Direction d : Direction.values())
			utility_accumulators.put(d, 0.0);

		// process all strategies
		for (Strategy s : strategies) {
			Map<Direction, Double> tmp_accumulators = s.process(this);
			if (tmp_accumulators != null)
				Utility.Add(utility_accumulators, tmp_accumulators);
		}
		// out-of-board check
		checkBoard(utility_accumulators);


		Direction dir = directionWithMaxUtility(utility_accumulators);
		//System.out.println("Goto:"+dir.toString());
		lastDirection = dir;
		return dir;
	}

	private Direction directionWithMaxUtility(
			Map<Direction, Double> utility_accumulators) {
		// iterator through each utility accumulator and return the direction
		// with maximal utility. If there are equal ones, randomly pick one.
		List<Direction> candidates = new ArrayList<Direction>();
		double maxUtility = Double.NEGATIVE_INFINITY;

		for (Direction dir : utility_accumulators.keySet()) {
			double utility = utility_accumulators.get(dir);
			if (utility > maxUtility) {
				maxUtility = utility;
			}
		}
		for (Direction dir : utility_accumulators.keySet()) {
			double utility = utility_accumulators.get(dir);
			if (utility == maxUtility) {
				candidates.add(dir);
			}
		}
		if (candidates.size() == 1)
			return candidates.get(0);
		else
			return candidates.get(random.nextInt(candidates.size()));
	}

	private void checkBoard(Map<Direction, Double> utility_accumulator) {
		int d2 =this.d;
		if(movDanger>=allHapp)
		{
			d2=1;	
			for (Direction dir : utility_accumulator.keySet()) {
				if (Math.abs(location.getX() + dir.dx) ==1 
						&& Math.abs(location.getY() + dir.dy) ==1)
					utility_accumulator.put(dir, Double.NEGATIVE_INFINITY);
			}
		}


		
		for (Direction dir : utility_accumulator.keySet()) {
			if (Math.abs(location.getX() + dir.dx) > d2
					|| Math.abs(location.getY() + dir.dy) > d2)
				utility_accumulator.put(dir, Double.NEGATIVE_INFINITY);
		}
		
	}
}
